

import asyncio
import json
import logging
import os
import random
import re
import shutil
# -*- coding: utf-8 -*-
import sys
import tempfile
import threading
import time
from configparser import ConfigParser
from datetime import datetime
from typing import Optional

import aiohttp
import psutil
import qasync
from PyQt5.QtCore import Qt, QTimer, pyqtSignal, QObject, QThread, QUrl, QMetaObject, QMutex, Q_ARG, pyqtSlot, \
    QMutexLocker
from PyQt5.QtGui import QColor, QIcon, QPalette
from PyQt5.QtMultimedia import QSoundEffect
from PyQt5.QtWidgets import (
    QMainWindow, QApplication, QWidget, QVBoxLayout, QHBoxLayout,
    QTextEdit, QPushButton, QTableWidget, QTableWidgetItem,
    QComboBox, QLabel, QStatusBar, QFileDialog, QMenu, QProgressBar,
    QHeaderView, QDialog, QSpinBox, QDialogButtonBox, QTabWidget, QMessageBox, QInputDialog, QScrollArea, QLineEdit,
    QAction
)
from openpyxl import load_workbook

from core.config.platform_parsers.weibo_parser import WeiboClient
from data_processor import DataProcessor
from export_handler import ExportHandler
from scheduler_manager import SchedulerManager
from security import ConfigEncryptor

logger = logging.getLogger(__name__)

class AsyncEventLoop:
    def __init__(self):
        self.loop = qasync.QEventLoop()
        asyncio.set_event_loop(self.loop)

    def __enter__(self):
        self.loop.__enter__()
        return self.loop

    def __exit__(self, exc_type, exc_val, exc_tb):
        """增强版退出处理"""
        logger.debug("开始关闭异步循环")
        # 1. 取消所有待处理任务
        for task in asyncio.all_tasks(self.loop):
            task.cancel()

        # 2. 执行清理
        self.loop.stop()
        self.loop.close()

        # 3. 确保事件循环重置
        asyncio.set_event_loop(None)
        logger.debug("异步循环已关闭")

class CustomTimerDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("自定义定时设置")
        self.setFixedSize(300, 150)
        layout = QVBoxLayout()

        self.input_hours = QSpinBox()
        self.input_hours.setMinimum(1)
        self.input_hours.setMaximum(744)
        self.input_hours.setSuffix(" 小时")

        btn_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        btn_box.accepted.connect(self.accept)
        btn_box.rejected.connect(self.reject)

        layout.addWidget(QLabel("请输入抓取间隔小时数："))
        layout.addWidget(self.input_hours)
        layout.addWidget(btn_box)
        self.setLayout(layout)

class CrawlerWorker(QObject):
    progress = pyqtSignal(int, dict)
    finished = pyqtSignal()
    error = pyqtSignal(str)

    def __init__(self, urls, parent=None):
        super().__init__(parent)
        self._urls = urls
        self._running = False
        self._sem = asyncio.Semaphore(5)
        self._session = None  # 延迟初始化
        self._loop = None  # 独立事件循环
        # 提前初始化处理器（可选）
        self.processor = DataProcessor()


    async def _init_session(self):
        """异步初始化session"""
        self._session = aiohttp.ClientSession(
            connector=aiohttp.TCPConnector(limit=3),
            timeout=aiohttp.ClientTimeout(total=20),
            headers={'User-Agent': 'Mozilla/5.0'}
        )

    async def _run_async(self):
        """异步任务入口"""
        try:
            await self._init_session()
            async with asyncio.TaskGroup() as tg:
                for idx, (url, platform) in enumerate(self._urls):
                    if not self._running:
                        break
                    task = tg.create_task(
                        self._fetch_with_retry(url, platform, idx)
                    )
                    await asyncio.sleep(0.1)
        except Exception as e:
            logger.exception("Worker Error")
            self.error.emit(str(e))
        finally:
            await self._cleanup()
            self.finished.emit()

    def run(self):
        """线程安全的主运行方法"""
        self._running = True
        try:
            # 创建新的事件循环
            self._loop = asyncio.new_event_loop()
            asyncio.set_event_loop(self._loop)

            # 运行异步任务
            self._loop.run_until_complete(self._run_async())
        except Exception as e:
            logger.error(f"Worker run failed: {e}")
        finally:
            # 清理资源
            if hasattr(self, '_loop') and self._loop:
                self._loop.close()

    async def _cleanup(self):
        """清理资源"""
        if self._session and not self._session.closed:
            await self._session.close()
        self._running = False

    async def start(self):
        try:
            self._loop = asyncio.new_event_loop()
            asyncio.set_event_loop(self._loop)
            await self._init_session()

            async with asyncio.TaskGroup() as tg:
                tasks = []
                for idx, (url, platform) in enumerate(self._urls):
                    if not self._running:
                        break
                    # 关键修复：括号闭合
                    task = tg.create_task(
                        self._fetch_with_retry(url, platform, idx)
                    )  # <-- 右括号补全
                    tasks.append(task)
                    await asyncio.sleep(0.1)

        except Exception as e:
            logger.exception("Worker Error")
            self.error.emit(str(e))
        finally:
            await self._cleanup()
            self.finished.emit()

    def stop(self):
        """线程安全的停止方法"""
        self._running = False
        if hasattr(self, '_loop') and self._loop and self._loop.is_running():
            # 取消所有任务
            for task in asyncio.all_tasks(self._loop):
                task.cancel()
            # 关闭事件循环
            self._loop.stop()

    async def _fetch_with_retry(
            self,
            url: str,
            platform: str,
            row_idx: int,
            retries: int = 3
    ) -> None:
        """完整且正确的异步请求方法（模块化版本）"""
        session = None
        try:
            # ===== 1. 初始化微博专属客户端 =====
            if platform == "微博":
                from core.config.platform_parsers.weibo_parser import WeiboClient
                # 从 DataProcessor 获取 Cookie
                cookie_str = self.processor.cookie_config.get(platform, 'cookie', fallback='')
                if not WeiboClient.validate_cookie(cookie_str):
                    raise ValueError("微博 Cookie 无效")
                async with WeiboClient(cookie_str) as client:
                    # 调用模块化解析逻辑 
                    result = await client.fetch_data(url)
            else:
                # 其他平台的通用逻辑（示例）
                async with aiohttp.ClientSession(
                        headers=self.processor._build_headers(platform),
                        cookies=self.processor._get_cookies(platform),
                        connector=aiohttp.TCPConnector(ssl=False)
                ) as session:
                    async with session.get(url) as response:
                        html = await response.text()
                        result = self.processor.platform_parsers[platform](html)

            # ===== 2. 发射结果 =====
            self.progress.emit(row_idx, result)

        except PermissionError as e:
            # 处理人工验证
            print(f"""
            [!] 需要人工验证：
            1. 访问: {str(e)}
            2. 更新 cookies.ini
            3. 按回车继续
            """)
            input()
            return await self._fetch_with_retry(url, platform, row_idx, retries)

        except Exception as e:
            # 错误重试逻辑
            if retries > 0:
                logger.warning(f"第 {4 - retries} 次重试 | 错误: {str(e)}")
                await asyncio.sleep(3 ** (3 - retries))  # 指数退避
                return await self._fetch_with_retry(url, platform, row_idx, retries - 1)

            self.progress.emit(row_idx, {
                'status': 'error',
                'message': f'最终失败: {str(e)}',
                'likes': 0,
                'comments': 0,
                'shares': 0
            })

        finally:
            if session and not session.closed:
                await session.close()

class CrawlerController(QObject):
    progress = pyqtSignal(int, dict)
    finished = pyqtSignal()
    error = pyqtSignal(str)

    def __init__(self, parent=None):
        super().__init__(parent)
        self._worker = None
        self._thread = None
        self._mutex = QMutex()
        self._stop_requested = False

    def is_running(self):
        """线程安全的状态检查"""
        with QMutexLocker(self._mutex):
            return self._thread is not None and self._thread.isRunning()

    def start(self, urls):
        with QMutexLocker(self._mutex):
            if self._thread and self._thread.isRunning():
                self.error.emit("已有任务在运行")
                return

            self._worker = CrawlerWorker(urls)
            self._thread = QThread()

            # 信号连接
            self._worker.progress.connect(self.progress)
            self._worker.finished.connect(self._on_worker_finished)
            self._worker.error.connect(self.error)

            self._worker.moveToThread(self._thread)
            self._thread.started.connect(self._worker.run)  # 直接调用run方法
            self._thread.start()

    def stop(self):
        """立即停止爬虫"""
        with QMutexLocker(self._mutex):
            if self._worker:
                self._worker.stop()
            if self._thread and self._thread.isRunning():
                self._thread.quit()
                # 添加超时等待
                if not self._thread.wait(500):  # 等待500ms
                    self._thread.terminate()

    def _on_worker_finished(self):
        """工作线程完成时的清理"""
        with QMutexLocker(self._mutex):
            if self._worker:
                self._worker.deleteLater()
                self._worker = None
            self.finished.emit()

class TabWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        layout = QVBoxLayout(self)  # 添加布局

        self.table = QTableWidget()
        self.table.setColumnCount(6)
        self.table.setHorizontalHeaderLabels(["URL", "平台", "点赞", "评论", "分享", "状态"])
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

        layout.addWidget(self.table)
        self._controller = None
        self._mutex = QMutex()

    def set_controller(self, controller):
        """由MainWindow统一设置控制器"""
        self._controller = controller

    def is_crawling(self):
        self._mutex.lock()
        try:
            running = self._controller and self._controller.is_running()
        finally:
            self._mutex.unlock()
        return running

class MainWindow(QMainWindow):
    update_status = pyqtSignal(str, str)
    progress_updated = pyqtSignal(int)
    # 状态和颜色常量
    STATUS_COLORS = {
        'normal': '#333333',
        'success': '#28a745',
        'warning': '#ffc107',
        'error': '#dc3545',
        'stopped': '#6c757d'
    }
    CELL_COLORS = {
        'error': '#FFC7CE',  # 浅红 - 错误状态
        'warning': '#FFEB9C',  # 浅黄 - 警告状态
        'success': '#C6EFCE',  # 浅绿 - 成功状态
        'default': '#FFFFFF'  # 白色 - 默认状态
    }
    # 状态文本常量
    STATUS_TEXTS = {
        'ready': '就绪',
        'running': '抓取中...',
        'stopping': '正在停止...',
        'stopped': '已停止',
        'error': '错误'
    }


    def __init__(self):
        super().__init__()

        # 初始化核心属性
        self.tabs = []  # 标签页列表
        self._controller = None  # 爬虫控制器
        self.loop = None  # 异步事件循环
        self.scheduler = SchedulerManager(parent=self)  # 初始化调度器  <--- 关键修复
        self.cookie_manager = ConfigEncryptor()  # Cookie管理器
        self.last_update_time = None  # 最后更新时间
        self.last_export_path = ''  # 最后导出路径
        self.is_running = False  # 运行状态标志
        self._mutex = QMutex()  # 线程锁

        # 初始化资源监控相关属性
        self.last_alert_time = None  # 最后报警时间
        self.alert_sound = QSoundEffect()  # 报警音效
        self.alert_blink_timer = QTimer()  # 报警闪烁定时器
        self.is_blinking = False  # 闪烁状态

        # 初始化状态栏标签
        self.status_labels = {
            'input': QLabel("就绪"),
            'count': QLabel("总任务: 0"),
            'progress': QProgressBar(),
            'last_crawl': QLabel("最后抓取: 从未")
        }

        # 初始化状态标签
        self.status_labels['input'].setText(self.STATUS_TEXTS['ready'])
        self.status_labels['input'].setStyleSheet(f"color: {self.STATUS_COLORS['normal']};")

        # 初始化UI和连接
        self.init_ui()
        self.init_connections()
        self.init_state()  # 保持原有调用位置

        # 延迟初始化异步组件
        QTimer.singleShot(0, self._delayed_async_init)

        # 加载配置
        self.load_config()

        # 初始化Cookie设置菜单
        menubar = self.menuBar()
        settings_menu = menubar.addMenu('设置')
        cookie_action = QAction('Cookie设置', self)
        cookie_action.triggered.connect(self.show_cookie_settings)
        settings_menu.addAction(cookie_action)
        self._check_file_permissions()
        # 确保cookies.ini文件存在且格式正确
        if not os.path.exists('cookies.ini'):
            self._init_default_cookie_file()
        else:
            # 验证文件格式
            try:
                with open('cookies.ini', 'r', encoding='utf-8-sig') as f:
                    content = f.read()
                    if not content.strip().startswith('['):
                        self._repair_cookie_file()
            except:
                self._repair_cookie_file()
        if not os.path.exists('cookies.ini'):
            with open('cookies.ini', 'w', encoding='utf-8') as f:
                config = ConfigParser()
                for platform in ["微博", "抖音", "百度贴吧", "今日头条", "快手",
                                 "小红书", "微信", "知乎", "哔哩哔哩", "ACFUN",
                                  "腾讯视频", "搜狐", "网易", "UC", "一点资讯"
                                 ,"百度度小视", "QQ浏览器", "好看视频", "网易新闻", "搜狐新闻"]:
                    config.add_section(platform)
                config.write(f)

    def _init_default_cookie_file(self):
        """创建默认的cookie配置文件"""
        config = ConfigParser()
        for platform in ["微博", "抖音", "百度贴吧", "今日头条", "快手", "小红书", "微信", "知乎",
                         "哔哩哔哩", "ACFUN", "腾讯视频", "搜狐", "网易", "UC", "一点资讯",
                         "百度度小视", "QQ浏览器", "好看视频", "网易新闻", "搜狐新闻"]:
            config[platform] = {'cookie': ''}

        with open('cookies.ini', 'w', encoding='utf-8') as f:
            config.write(f)

    def save_cookies_to_alternate_location(self):
        """允许用户选择其他位置保存Cookie配置"""
        options = QFileDialog.Options()
        file_path, _ = QFileDialog.getSaveFileName(
            self,
            "选择Cookie配置文件保存位置",
            "",
            "INI Files (*.ini);;All Files (*)",
            options=options
        )

        if file_path:
            try:
                with open(file_path, 'w', encoding='utf-8') as f:
                    self.config.write(f)
                QMessageBox.information(self, "成功", f"配置已保存到:\n{file_path}")
                return True
            except Exception as e:
                QMessageBox.critical(self, "错误", f"保存失败:\n{str(e)}")
                return False
        return False

    def _check_file_permissions(self):
        """检查必要的文件权限"""
        try:
            # 检查cookies.ini文件
            cookie_file = 'cookies.ini'
            if os.path.exists(cookie_file):
                if not os.access(cookie_file, os.W_OK):
                    logger.warning(f"没有{cookie_file}的写入权限")
                    QMessageBox.warning(
                        self,
                        "权限警告",
                        f"程序没有{cookie_file}文件的写入权限，Cookie设置可能无法保存\n"
                        "请检查文件权限或尝试以管理员身份运行程序"
                    )

            # 检查程序所在目录是否可写
            if not os.access(os.getcwd(), os.W_OK):
                logger.warning("程序目录不可写")
                QMessageBox.warning(
                    self,
                    "权限警告",
                    "程序所在目录不可写，这可能导致功能异常\n"
                    "建议将程序安装到有写入权限的目录"
                )
        except Exception as e:
            logger.error(f"权限检查失败: {str(e)}")

    def _repair_cookie_file(self):
        """修复损坏的cookie文件"""
        try:
            # 备份原文件
            if os.path.exists('cookies.ini'):
                os.rename('cookies.ini', 'cookies.ini.bak')

            # 创建新文件
            self._init_default_cookie_file()
        except Exception as e:
            logger.error(f"修复cookie文件失败: {str(e)}")

    def _delayed_async_init(self):
        """主线程异步初始化"""
        try:
            # 主事件循环只处理UI相关异步
            self.loop = qasync.QEventLoop(self)
            asyncio.set_event_loop(self.loop)

            # 初始化控制器
            self._controller = CrawlerController()
            self._setup_controller_connections()

        except Exception as e:
            logger.error(f"Async init failed: {str(e)}")

    def _init_async(self):
        """安全初始化异步环境"""
        try:
            self.loop = qasync.QEventLoop(QApplication.instance())
            asyncio.set_event_loop(self.loop)

            # 初始化控制器
            self._controller = CrawlerController()
            self._setup_controller_connections()

            # 调度器已在 __init__ 中初始化  <--- 修改点
            self.scheduler.resource_alert.connect(self.handle_resource_alert)
            self.scheduler.safe_start_scheduler()

        except Exception as e:
            logger.error(f"异步初始化失败: {str(e)}")
            QMessageBox.critical(self, "错误", "异步环境初始化失败")

    def _setup_controller_connections(self):
        """集中管理信号连接"""
        self._controller.progress.connect(self._update_crawl_progress)
        self._controller.finished.connect(
            lambda: self._on_crawl_finished(safe=True))
        self._controller.error.connect(
            lambda msg: self.update_status.emit('error', msg))

    def show_cookie_settings(self):
        """安全显示Cookie设置对话框"""
        try:
            # 确保在主线程执行
            if QThread.currentThread() != self.thread():
                QMetaObject.invokeMethod(self, "show_cookie_settings", Qt.QueuedConnection)
                return

            # 检查现有对话框
            if hasattr(self, '_cookie_dialog') and self._cookie_dialog.isVisible():
                self._cookie_dialog.activateWindow()
                return

            # 创建新对话框（非模态）
            self._cookie_dialog = CookieSettingsDialog(self)
            self._cookie_dialog.setAttribute(Qt.WA_DeleteOnClose)
            self._cookie_dialog.finished.connect(lambda: delattr(self, '_cookie_dialog'))
            self._cookie_dialog.show()  # 使用show()而非exec_()避免阻塞

        except Exception as e:
            logger.error(f"打开设置失败: {str(e)}")
            QMessageBox.critical(self, "错误", "无法打开设置窗口")

    def show_warning(self, message):
        """非阻塞式警告显示"""
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Warning)
        msg.setText(message)
        msg.setWindowTitle("系统警告")
        msg.show()  # 使用show()代替exec_()避免阻塞

    #报警系统

    def _init_alert_system(self):
        """优化后的报警系统初始化"""
        # 合并为一个定时器
        self.alert_timer = QTimer()
        self.alert_timer.timeout.connect(self._check_and_update_alerts)
        self.alert_timer.start(5000)  # 5秒检查一次

        # 报警音效
        self.alert_sound = QSoundEffect()
        self.alert_sound.setSource(QUrl.fromLocalFile('resources/alert.wav'))

    def _check_and_update_alerts(self):
        """合并的报警检查方法"""
        # 检查资源
        self._check_resources()

        # 更新UI状态
        if self.is_blinking:
            self._update_status_ui()

    def _update_status_ui(self):
        """优化后的状态栏更新方法（带更新频率限制）"""
        # 获取当前时间（秒级精度）
        current_time = time.time()

        # 频率限制检查（每秒最多更新10次）
        if hasattr(self, '_last_ui_update') and current_time - self._last_ui_update < 0.1:
            return

        # 记录最后更新时间
        self._last_ui_update = current_time

        # 获取当前报警状态
        alert_level = 'normal'
        if hasattr(self, 'current_alert_level'):
            alert_level = self.current_alert_level

        # 设置状态文本颜色
        color_map = {
            'critical': '#ff4444',  # 红色
            'warning': '#ffc107',  # 黄色
            'normal': '#333333'  # 默认
        }
        color = color_map.get(alert_level, '#333333')

        # 线程安全的UI更新
        def _safe_update():
            self.status_labels['input'].setStyleSheet(f"color: {color};")
            if alert_level == 'critical':
                # 紧急状态添加闪烁效果
                current_color = self.status_labels['input'].palette().color(QPalette.WindowText)
                new_color = QColor('red') if current_color != QColor('red') else QColor('white')
                self.status_labels['input'].setStyleSheet(f"color: {new_color.name()};")

        # 确保在主线程执行UI更新
        QTimer.singleShot(0, _safe_update)

    def handle_resource_alert(self, level, message):
        """处理报警信号"""
        """初始化报警处理"""
        # 连接报警信号
        self.scheduler.resource_alert.connect(self.handle_resource_alert)

        # 报警状态追踪
        self.last_alert_time = None

        # 报警音效准备
        self.alert_sound = QSoundEffect()
        self.alert_sound.setSource(QUrl.fromLocalFile('resources/alert.wav'))
        self.alert_blink_timer = QTimer()
        self.alert_blink_timer.timeout.connect(self._blink_status)
        self.is_blinking = False
        # 冷却时间检查
        if self._check_cooldown():
            return

        # 视觉报警
        self._show_alert_dialog(level, message)

        # 声音报警
        if level == 'critical':
            self.alert_sound.play()

        # 状态栏提示
        self.status_labels['input'].setText(message)
        self.status_labels['input'].setStyleSheet(
            "color: red;" if level == 'critical' else "color: orange;"
        )

        # 记录报警时间
        self.last_alert_time = datetime.now()
        if level == 'critical':
            self.alert_blink_timer.start(500)  # 0.5秒闪烁
        else:
            self.alert_blink_timer.stop()

    def _check_cooldown(self):
        """检查报警冷却时间（支持动态配置）

        Returns:
            bool: 是否处于冷却期内（True=跳过报警，False=允许报警）
        """
        # 1. 如果没有上次报警时间，直接允许报警
        if not self.last_alert_time:
            return False

        # 2. 获取冷却时间配置（默认5分钟）
        try:
            cooldown = int(getattr(self.scheduler, 'cooldown', 300))  # 默认300秒(5分钟)
        except (AttributeError, ValueError):
            cooldown = 300  # 异常时使用默认值

        # 3. 计算时间差（秒）
        elapsed = (datetime.now() - self.last_alert_time).total_seconds()

        # 4. 动态日志记录（仅当接近冷却时间时记录）
        if 0 < cooldown - elapsed < 10:  # 冷却结束前10秒内
            logger.debug(f"报警冷却即将结束，剩余{cooldown - elapsed:.1f}秒")

        return elapsed < cooldown

    def _show_alert_dialog(self, level, message):
        """显示报警对话框"""
        dialog = QMessageBox(self)
        dialog.setWindowTitle("系统报警" if level == 'critical' else "系统警告")
        dialog.setText(message)

        # 设置图标
        if level == 'critical':
            dialog.setIcon(QMessageBox.Critical)
        elif level == 'warning':
            dialog.setIcon(QMessageBox.Warning)
        else:
            dialog.setIcon(QMessageBox.Information)

        # 添加确认按钮
        dialog.addButton(QMessageBox.Ok)
        dialog.exec_()

    def _init_resource_indicator(self):
        """添加资源状态指示器"""
        self.resource_label = QLabel()
        self.resource_label.setToolTip("实时系统资源监控\n绿色:正常 黄色:警告 红色:危险")
        self.status_bar.addPermanentWidget(self.resource_label)

        # 启动资源更新定时器
        self.resource_timer = QTimer()
        self.resource_timer.timeout.connect(self.update_resource_ui)
        self.resource_timer.start(5000)  # 5秒更新一次

    def update_resource_ui(self):
        """更新资源显示"""
        try:
            import psutil
            cpu = psutil.cpu_percent()
            mem = psutil.virtual_memory().percent

            status_text = f"CPU: {cpu}% MEM: {mem}%"

            # 根据阈值设置颜色
            if mem > 90 or cpu > 90:
                color = '#ff4444'  # 红色
            elif mem > 70 or cpu > 70:
                color = '#ffc107'  # 黄色
            else:
                color = '#28a745'  # 绿色

            self.resource_label.setText(status_text)
            self.resource_label.setStyleSheet(f"color: {color}; font-weight: bold;")
        except ImportError:
            self.resource_label.setText("资源监控未启用")
            self.resource_label.setStyleSheet("color: #6c757d;")

    def init_ui(self):
        self.setWindowTitle('多平台数据采集系统 v2.0')
        self.setMinimumSize(1200, 800)
        self.setWindowIcon(QIcon('resources/icon.png'))

        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        main_layout = QVBoxLayout(main_widget)

        # 输入区域
        input_layout = QHBoxLayout()
        self.text_input = QTextEdit()
        self.text_input.setPlaceholderText("支持直接粘贴链接（每行一条）或点击下方Excel导入...")
        self.text_input.setFixedHeight(150)
        input_layout.addWidget(self.text_input, 3)

        # 操作按钮
        btn_layout = QVBoxLayout()
        self.import_btn = QPushButton("Excel导入")
        self.import_btn.setIcon(QIcon('resources/excel_icon.png'))
        self.identify_btn = QPushButton("识别链接")
        self.start_btn = QPushButton("开始抓取")

        # 定时器下拉框
        self.timer_combo = QComboBox()
        self.timer_combo.blockSignals(True)
        self.timer_combo.addItem("立即执行", 0)
        self.timer_combo.addItem("每30分钟", 30)
        self.timer_combo.addItem("每小时", 60)
        self.timer_combo.addItem("自定义...", -1)
        self.timer_combo.setCurrentIndex(0)
        self.timer_combo.blockSignals(False)

        # 按钮样式
        for btn in [self.import_btn, self.identify_btn, self.start_btn]:
            btn.setFixedSize(120, 40)
            btn.setStyleSheet("QPushButton { padding: 5px; }")
            btn_layout.addWidget(btn)

        btn_layout.addWidget(self.timer_combo)
        input_layout.addLayout(btn_layout, 1)
        main_layout.addLayout(input_layout)

        # 标签页系统
        self.tab_widget = QTabWidget()
        self.tab_widget.setTabsClosable(True)
        self.tab_widget.tabCloseRequested.connect(self.close_tab)
        self.add_new_tab("主任务")

        # 添加新标签按钮
        self.new_tab_btn = QPushButton("+")
        self.new_tab_btn.clicked.connect(lambda: self.add_new_tab(f"任务{self.tab_widget.count() + 1}"))
        self.tab_widget.setCornerWidget(self.new_tab_btn)

        main_layout.addWidget(self.tab_widget)

        # 全局状态栏
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)

        self.status_labels = {
            'input': QLabel("就绪"),
            'count': QLabel("总任务: 0"),
            'progress': QProgressBar(),
            'last_crawl': QLabel("最后抓取: 从未")
        }

        self.status_labels['progress'].setFixedWidth(200)
        self.status_labels['progress'].setRange(0, 100)

        self.status_bar.addWidget(self.status_labels['input'])
        self.status_bar.addPermanentWidget(self.status_labels['progress'])
        self.status_bar.addPermanentWidget(self.status_labels['count'])
        self.status_bar.addPermanentWidget(self.status_labels['last_crawl'])

        # 右键菜单
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.show_context_menu)

        # 设置表格样式
        header = self.current_tab().table.horizontalHeader()
        header.setSectionResizeMode(0, QHeaderView.Stretch)
        header.setSectionResizeMode(1, QHeaderView.ResizeToContents)
        for i in range(2, 6):
            header.setSectionResizeMode(i, QHeaderView.ResizeToContents)

    def _stop_crawling(self):
        if not self._controller:
            return

        if self._controller.is_running():
            self._controller.stop()
            self.start_btn.setText('开始抓取')
            self.start_btn.setStyleSheet('')

    def add_new_tab(self, title):
        new_tab = TabWidget()
        if hasattr(self, '_controller'):  # 保护性检查
            new_tab.set_controller(self._controller)
        new_tab.table.setColumnCount(6)
        new_tab.table.setHorizontalHeaderLabels(["URL", "平台", "点赞", "评论", "分享", "状态"])  # 添加列标题
        self.tab_widget.addTab(new_tab, title)
        self.tabs.append(new_tab)
        self.tab_widget.setCurrentIndex(len(self.tabs) - 1)
        new_tab.set_controller(self._controller)  # 共享控制器

    def current_tab(self) -> TabWidget:
        return self.tab_widget.currentWidget()

    def close_tab(self, index):
        if self.tab_widget.count() > 1:
            widget = self.tab_widget.widget(index)
            widget.close()
            self.tab_widget.removeTab(index)
            self.tabs.pop(index)
        else:
            self.update_status.emit('warning', '至少保留一个标签页')

    def init_connections(self):
        self.text_input.textChanged.connect(self.update_input_counter)
        self.import_btn.clicked.connect(self.handle_import)
        self.identify_btn.clicked.connect(self.identify_links)
        self.start_btn.clicked.connect(self.toggle_crawler)
        self.timer_combo.currentTextChanged.connect(self.handle_timer_change)
        self.update_status.connect(self.handle_status_update)
        self.progress_updated.connect(self.status_labels['progress'].setValue)

    def init_state(self):
        """初始化程序状态"""
        # 初始化调度器连接
        if hasattr(self, 'scheduler') and self.scheduler:  # 安全检查
            self.scheduler.resource_alert.connect(self.handle_resource_alert)

        self.is_running = False
        self.cookie_manager = ConfigEncryptor()
        self.last_update_time = None
        self.last_export_path = ''

        # 使用主线程定时器启动调度器
        QTimer.singleShot(1000, self.safe_start_scheduler)

    def safe_start_scheduler(self):
        """在主线程上下文启动调度器"""
        if self.scheduler.scheduler.running:
            return
        try:
            # 配置线程池使用Qt兼容的工作线程
            self.scheduler.scheduler.configure(
                executors={'default': {'type': 'threadpool', 'max_workers': 5}},
                job_defaults={'coalesce': True}
            )
            self.scheduler.scheduler.start()
        except Exception as e:
            print(f"调度器启动失败: {str(e)}")

    def load_config(self):
        config = ConfigParser()
        if os.path.exists('config.ini'):
            config.read('config.ini')
            if config.has_option('Schedule', 'CustomIntervals'):
                intervals = json.loads(config.get('Schedule', 'CustomIntervals'))
                for text, minutes in intervals.items():
                    self._update_timer_combo(text, minutes)

    def update_input_counter(self):
        text = self.text_input.toPlainText()
        lines = [ln.strip() for ln in text.split('\n') if ln.strip()]
        unique_lines = list(set(lines))[:500]
        count = len(unique_lines)

        self.status_labels['count'].setText(f"总任务: {count}")
        if count >= 500:
            self.update_status.emit('warning', '已达到最大输入限制（500条）')

    def handle_import(self):
        path, _ = QFileDialog.getOpenFileName(
            self, "选择文件", "",
            "Excel文件 (*.xlsx *.xls);;CSV文件 (*.csv);;文本文件 (*.txt)"
        )

        if not path:
            return

        try:
            if path.endswith('.txt'):
                with open(path, 'r', encoding='utf-8') as f:
                    lines = f.readlines()[1:] if f.readline().startswith(('http', 'www')) else f.readlines()
                    links = [line.strip() for line in lines[:200] if line.strip()]
            elif path.endswith('.csv'):
                import csv
                links = []
                with open(path, 'r', encoding='utf-8-sig') as f:  # 使用utf-8-sig处理BOM头
                    reader = csv.reader(f)
                    # 调试：打印标题行确认结构
                    headers = next(reader, None)
                    print(f"CSV标题行: {headers}")  # 调试用

                    for row_idx, row in enumerate(reader, 1):
                        if not row:  # 空行跳过
                            continue

                        # 尝试从不同列获取链接
                        url = None
                        for col in row:
                            if isinstance(col, str) and col.strip().startswith(('http://', 'https://')):
                                url = col.strip()
                                break

                        if url:
                            # 放宽过滤条件，只检查基本URL格式
                            if any(c in url for c in ['\u4e00-\u9fff', ' ']):  # 保留原有中文过滤
                                print(f"跳过含中文的行 {row_idx}: {url}")  # 调试用
                                continue
                            links.append(url)
                            print(f"添加链接 {row_idx}: {url}")  # 调试用
                        else:
                            print(f"未找到有效URL的行 {row_idx}: {row}")  # 调试用

                        if len(links) >= 500:
                            break
            else:
                wb = load_workbook(path)
                ws = wb.active
                links = []
                for row in ws.iter_rows(min_row=2, max_col=1):
                    cell_value = str(row[0].value).strip()
                    if cell_value and not any(c in '\u4e00-\u9fff' for c in cell_value):
                        links.append(cell_value)
                    if len(links) >= 500:
                        break

            print(f"最终导入链接数: {len(links)}")  # 调试用
            self.text_input.setText('\n'.join(links))
            self.update_status.emit('success', f'成功导入 {len(links)} 条链接')
            self.update_input_counter()
        except Exception as e:
            self.update_status.emit('error', f'文件导入失败: {str(e)}')
            print(f"导入错误: {str(e)}")  # 调试用

    def identify_links(self):
        try:
            raw_text = self.text_input.toPlainText()
            if not raw_text.strip():
                self.update_status.emit('warning', '请输入链接内容')
                return

            # 分割文本行并过滤空行
            lines = [ln.strip() for ln in raw_text.split('\n') if ln.strip()]

            # 如果第一行看起来像标题（包含中文或没有URL特征），则跳过
            if lines and not DataProcessor.is_likely_url(lines[0]):
                lines = lines[1:]

            if not lines:
                self.update_status.emit('warning', '没有有效的链接内容')
                return

            current_tab = self.current_tab()
            if not current_tab:
                self.update_status.emit('error', '没有可用的标签页')
                return

            # 设置表格行数和列数
            current_tab.table.setRowCount(len(lines))
            current_tab.table.setColumnCount(6)
            current_tab.table.setHorizontalHeaderLabels(["URL", "平台", "点赞", "评论", "分享", "状态"])

            # 验证链接并填充表格
            valid_links = DataProcessor.validate_links(lines)
            for row, (link, platform) in enumerate(valid_links):
                # URL列
                url_item = QTableWidgetItem(link)
                current_tab.table.setItem(row, 0, url_item)

                # 平台列
                platform_item = QTableWidgetItem(platform)
                current_tab.table.setItem(row, 1, platform_item)

                # 初始化其他列
                for col in range(2, 6):
                    item = QTableWidgetItem('待抓取')
                    item.setTextAlignment(Qt.AlignCenter)
                    current_tab.table.setItem(row, col, item)

            self.update_status.emit('success', f'已识别 {len(valid_links)} 条链接')

        except Exception as e:
            logger.error(f"链接识别错误: {str(e)}", exc_info=True)
            self.update_status.emit('error', f'链接识别失败: {str(e)}')

    def toggle_crawler(self):
        if not self._controller:
            return

        if self._controller.is_running():
            self._safe_stop_crawler()
        else:
            self._safe_start_crawler()

    def _safe_start_crawler(self):
        """安全启动爬虫任务"""
        try:
            # 检查控制器是否已初始化
            if not self._controller:
                self.update_status.emit('error', '控制器未初始化')
                return

            # 检查是否已在运行
            if self._controller.is_running():
                self.update_status.emit('warning', '已有任务在运行')
                return

            # 获取当前标签页
            current_tab = self.current_tab()
            if not current_tab:
                self.update_status.emit('warning', '没有可用的标签页')
                return

            # 准备URL列表
            urls = []
            for row in range(current_tab.table.rowCount()):
                url_item = current_tab.table.item(row, 0)  # URL列
                platform_item = current_tab.table.item(row, 1)  # 平台列

                if url_item and platform_item and url_item.text():
                    urls.append((url_item.text(), platform_item.text()))

                    # 重置状态为"抓取中"
                    status_item = current_tab.table.item(row, 5)
                    if not status_item:
                        status_item = QTableWidgetItem()
                        status_item.setTextAlignment(Qt.AlignCenter)
                        current_tab.table.setItem(row, 5, status_item)
                    status_item.setText('抓取中')
                    status_item.setBackground(QColor(self.CELL_COLORS['default']))

            if not urls:
                self.update_status.emit('warning', '没有可抓取的URL')
                return

            # 启动爬虫前的UI更新
            self.start_btn.setText('停止抓取')
            self.start_btn.setStyleSheet(
                f'background-color: {self.STATUS_COLORS["error"]};'
                'color: white;'
            )
            self.status_labels['progress'].setValue(0)
            self.update_status.emit('info', self.STATUS_TEXTS['running'])
            self.status_labels['last_crawl'].setText(
                f"最后抓取: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
            )

            # 启动爬虫
            self._controller.start(urls)

        except Exception as e:
            logger.error(f"启动爬虫失败: {str(e)}", exc_info=True)
            self.update_status.emit('error', f'启动失败: {str(e)}')

            # 恢复UI状态
            self.start_btn.setText('开始抓取')
            self.start_btn.setStyleSheet('')
            self.status_labels['progress'].setValue(0)

    def _safe_stop_crawler(self):
        """立即停止爬虫并同步更新所有UI状态"""
        try:
            # 立即更新UI状态（不等待线程停止）
            self.start_btn.setText('开始抓取')
            self.start_btn.setStyleSheet('')
            self.status_labels['progress'].setValue(0)  # 立即重置进度条
            self.update_status.emit('info', self.STATUS_TEXTS['stopped'])

            # 记录停止时间（立即更新）
            stop_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            self.status_labels['last_crawl'].setText(f"最后抓取: {stop_time}")

            # 停止控制器（异步操作）
            self._controller.stop()

            # 停止所有动画效果
            self.alert_blink_timer.stop()
            self.status_labels['input'].setStyleSheet('')

            # 更新表格中所有"抓取中"的状态
            current_tab = self.current_tab()
            if current_tab:
                for row in range(current_tab.table.rowCount()):
                    status_item = current_tab.table.item(row, 5)  # 状态列
                    if status_item and status_item.text() == '抓取中':
                        status_item.setText('已停止')
                        status_item.setBackground(QColor(self.CELL_COLORS['default']))

            # 强制刷新UI（确保立即更新）
            QApplication.processEvents()

        except Exception as e:
            logger.error(f"停止爬虫时出错: {str(e)}", exc_info=True)
            self.update_status.emit('error', f'停止失败: {str(e)}')

    def _start_crawling(self):
        """启动爬虫任务（线程安全）"""
        # 1. 强制重新加载配置
        DataProcessor()._safe_load_cookies()
        # 2. 验证配置有效性
        if not self._validate_cookie_config():
            raise ValueError("Cookie配置无效")
        current_tab = self.current_tab()
        if not current_tab:
            self.update_status.emit('warning', '没有可用的标签页')
            return

        try:
            # 获取表格数据（线程安全）
            urls = []
            for row in range(current_tab.table.rowCount()):
                url_item = current_tab.table.item(row, 0)
                platform_item = current_tab.table.item(row, 1)
                if url_item and platform_item and url_item.text():
                    urls.append((url_item.text(), platform_item.text()))

            if not urls:
                raise ValueError("没有可抓取的URL")
            self._controller.start(urls)
            self.update_status('info', '爬虫已启动')

            # 使用控制器锁保证线程安全
            if self._controller.is_running():
                self.update_status.emit('warning', '已有任务在运行')
                return

            # 启动爬虫
            self._controller.start(urls)

            # 更新UI状态
            self.start_btn.setText('停止抓取')
            self.start_btn.setStyleSheet('background-color: #ff4444;')
            self.update_status.emit('info', '爬虫已启动')


        except Exception as e:
            logger.error(f"启动失败: {str(e)}", exc_info=True)
            self.update_status('error', f'启动失败: {str(e)}')

    def _validate_cookie_config(self) -> bool:
        """验证Cookie配置是否有效"""
        try:
            dp = DataProcessor()
            for platform in ['微博', '抖音']:  # 关键平台检查
                if platform in dp.cookie_config:
                    cookie = dp.cookie_config[platform].get('cookie', '')
                    if cookie and len(cookie) > 10:  # 简单长度验证
                        return True
            return False
        except:
            return False

    def _start_crawling_safely(self):
        current_tab = self.current_tab()
        urls = self._prepare_urls()  # 提取URL的准备方法

        if current_tab.start_crawling(urls):
            self.start_btn.setText('停止抓取')
            self.start_btn.setStyleSheet('background-color: #ff4444;')
            self.update_status('info', '爬虫已启动')

    def _prepare_urls(self):
        """准备URL列表（线程安全）"""
        current_tab = self.current_tab()
        urls = []
        for row in range(current_tab.table.rowCount()):
            url_item = current_tab.table.item(row, 0)
            platform_item = current_tab.table.item(row, 1)
            if url_item and platform_item:
                urls.append((url_item.text(), platform_item.text()))
        return urls

    def _stop_crawling_safely(self):
        current_tab = self.current_tab()
        current_tab.stop_crawling()

        # 非阻塞式等待停止完成
        QTimer.singleShot(100, lambda: self._check_stop_completion(current_tab))

    def _check_stop_completion(self, tab):
        if tab.is_crawling():
            QTimer.singleShot(100, lambda: self._check_stop_completion(tab))
        else:
            self.start_btn.setText('开始抓取')
            self.start_btn.setStyleSheet('')
            self.update_status('info', '爬虫已停止')

    def _update_crawl_progress(self, row, data):
        """
        更新抓取进度和表格数据（线程安全）
        :param row: 行索引
        :param data: 包含抓取结果的字典
        """
        if not hasattr(self, '_controller') or not self._controller.is_running():
            return
        if not data or not isinstance(data, dict):
            logger.error(f"无效的进度数据: {data}")
            data = {
                'status': 'error',
                'message': '无效的进度数据',
                'likes': 0,
                'comments': 0,
                'shares': 0
            }

        def safe_update():
            try:
                current_tab = self.current_tab()
                if not current_tab or row >= current_tab.table.rowCount():
                    return

                # 获取状态和错误信息
                status = data.get('status', 'success')
                is_error = status == 'error'
                error_msg = data.get('message', '未知错误') if is_error else ''

                # 更新表格数据
                columns = [
                    ('likes', 2, '0'),
                    ('comments', 3, '0'),
                    ('shares', 4, '0'),
                    ('status', 5, '抓取失败' if is_error else '成功')
                ]

                for key, col, default in columns:
                    value = str(data.get(key, default))
                    item = current_tab.table.item(row, col)

                    if not item:
                        item = QTableWidgetItem(value)
                        item.setTextAlignment(Qt.AlignCenter)
                        current_tab.table.setItem(row, col, item)
                    else:
                        item.setText(value)

                    # 设置背景色
                    bg_color = self.CELL_COLORS['error'] if is_error else self.CELL_COLORS['default']
                    item.setBackground(QColor(bg_color))

                # 更新进度条
                progress = int((row + 1) / current_tab.table.rowCount() * 100)
                self.status_labels['progress'].setValue(progress)

                # 更新状态栏
                if is_error:
                    self.update_status.emit('error', f'抓取失败: {error_msg}')
                else:
                    success_count = sum(1 for i in range(current_tab.table.rowCount())
                                        if current_tab.table.item(i, 5) and
                                        current_tab.table.item(i, 5).text() == '成功')
                    self.update_status.emit(
                        'info',
                        f'抓取中: {success_count}/{current_tab.table.rowCount()} 成功'
                    )

            except Exception as e:
                logger.error(f"更新抓取进度异常: {str(e)}", exc_info=True)

        # 使用线程安全方式更新UI
        QMetaObject.invokeMethod(
            self,
            "execute_safe_update",
            Qt.QueuedConnection,
            Q_ARG(int, row),
            Q_ARG(dict, data)
        )

    @pyqtSlot(int, dict)
    def execute_safe_update(self, row, data):
        """
        线程安全的UI更新槽函数
        处理抓取进度更新和表格数据刷新

        参数:
            row (int): 需要更新的行索引
            data (dict): 包含抓取结果的数据字典，格式:
                {
                    'status': 'success'/'error',
                    'likes': int,
                    'comments': int,
                    'shares': int,
                    'message': str (当status为error时存在)
                }
        """
        try:
            # 获取当前标签页（线程安全方式）
            current_tab = self.current_tab()
            if not current_tab:
                logger.warning("执行更新时未找到当前标签页")
                return

            if row >= current_tab.table.rowCount():
                logger.warning(f"行索引越界: {row} (总行数: {current_tab.table.rowCount()})")
                return

            # 解析数据状态
            status = data.get('status', 'success')
            is_error = status == 'error'
            error_msg = data.get('message', '未知错误') if is_error else ''

            # 准备表格列映射
            columns = [
                ('likes', 2, '0'),  # (数据键, 列索引, 默认值)
                ('comments', 3, '0'),
                ('shares', 4, '0'),
                ('status', 5, '抓取失败' if is_error else '成功')
            ]

            # 更新表格单元格
            for key, col, default in columns:
                # 获取或创建表格项
                item = current_tab.table.item(row, col)
                if not item:
                    item = QTableWidgetItem()
                    item.setTextAlignment(Qt.AlignCenter)
                    current_tab.table.setItem(row, col, item)

                # 设置文本值
                item.setText(str(data.get(key, default)))

                # 设置背景色
                bg_color = self.CELL_COLORS['error'] if is_error else self.CELL_COLORS['default']
                item.setBackground(QColor(bg_color))

            # 更新进度条（线程安全操作）
            progress = int((row + 1) / current_tab.table.rowCount() * 100)
            self.status_labels['progress'].setValue(progress)

            # 更新状态栏信息
            if is_error:
                self.update_status.emit('error', f'抓取失败: {error_msg}')
            else:
                # 计算成功数量
                success_count = sum(
                    1 for i in range(current_tab.table.rowCount())
                    if current_tab.table.item(i, 5) and
                    current_tab.table.item(i, 5).text() == '成功'
                )
                total = current_tab.table.rowCount()
                self.update_status.emit(
                    'info',
                    f'进度: {success_count}/{total} | ' +
                    f'成功率: {success_count / total:.1%}' if total > 0 else '0%'
                )

        except Exception as e:
            logger.error(f"执行安全更新时发生异常: {str(e)}", exc_info=True)
            # 即使出错也尝试更新状态栏
            self.update_status.emit(
                'error',
                f'UI更新错误: {str(e)[:100]}...'
            )

    def _on_crawl_finished(self, safe=False):
        """增加安全关闭标志"""
        if not safe:
            self.update_status.emit('warning', '线程非正常结束')
        self.is_running = False
        self.start_btn.setText('开始抓取')
        self.start_btn.setStyleSheet('')

    def show_context_menu(self, pos):
        menu = QMenu()

        # 原有菜单项
        export_all_action = menu.addAction("导出全部数据")
        export_selected_action = menu.addAction("导出选中数据")
        copy_action = menu.addAction("复制选中行")
        clear_action = menu.addAction("清空")

        # 新增菜单项
        crawl_selected_action = menu.addAction("立即抓取所选链接")

        current_tab = self.current_tab()
        has_data = current_tab.table.rowCount() > 0
        has_selection = len(current_tab.table.selectedItems()) > 0

        # 设置可用性
        export_all_action.setEnabled(has_data)
        export_selected_action.setEnabled(has_selection)
        copy_action.setEnabled(has_selection)
        crawl_selected_action.setEnabled(has_selection)
        clear_action.setEnabled(has_data)

        # 连接信号
        export_all_action.triggered.connect(self.export_all_data)
        export_selected_action.triggered.connect(self.export_selected_data)
        copy_action.triggered.connect(self.copy_selected_rows)
        clear_action.triggered.connect(lambda: self.handle_clear(self.current_tab().table))
        crawl_selected_action.triggered.connect(self.crawl_selected_links)

        menu.exec_(self.mapToGlobal(pos))

    def crawl_selected_links(self):
        """立即抓取选中的链接"""
        if self._controller and self._controller.is_running():
            self.update_status.emit('warning', '已有任务在运行，请先停止当前任务')
            return

        current_tab = self.current_tab()
        selected_rows = {item.row() for item in current_tab.table.selectedItems()}

        if not selected_rows:
            self.update_status.emit('warning', '没有选中的链接')
            return

        # 准备URL列表
        urls = []
        for row in selected_rows:
            url_item = current_tab.table.item(row, 0)
            platform_item = current_tab.table.item(row, 1)
            if url_item and platform_item:
                urls.append((url_item.text(), platform_item.text()))

                # 重置状态为"待抓取"
                for col in range(2, 6):
                    item = current_tab.table.item(row, col)
                    if item:
                        item.setText('待抓取')
                        item.setBackground(QColor('#FFFFFF'))  # 重置背景色

        if not urls:
            self.update_status.emit('warning', '没有有效的链接')
            return

        # 启动爬虫
        self._controller.start(urls)
        self.start_btn.setText('停止抓取')
        self.start_btn.setStyleSheet('background-color: #ff4444;')
        self.update_status.emit('info', f'开始抓取 {len(urls)} 条选中链接...')

    def handle_clear(self, widget):                                     #清空方法
        if isinstance(widget, QTextEdit):
            widget.clearContents()  # 清空表格内容但保留表头
            widget.setRowCount(0)  # 移除所有行
            self.update_status.emit('info', '输入框已清空')
        elif isinstance(widget, QTableWidget):
            for item in widget.selectedItems():
                item.setText('')
            self.update_status.emit('info', '选中单元格已清空')

    def export_all_data(self):
        self._export_data(range(self.current_tab().table.rowCount()))

    def export_selected_data(self):
        selected_rows = {item.row() for item in self.current_tab().table.selectedItems()}
        self._export_data(sorted(selected_rows))

    def _export_data(self, rows):
        try:
            current_tab = self.current_tab()
            data = []
            for row in rows:
                try:
                    data.append({
                        'url': current_tab.table.item(row, 0).text() if current_tab.table.item(row, 0) else '',
                        'platform': current_tab.table.item(row, 1).text() if current_tab.table.item(row, 1) else '未知',
                        'likes': current_tab.table.item(row, 2).text() if current_tab.table.item(row, 2) else '0',
                        'comments': current_tab.table.item(row, 3).text() if current_tab.table.item(row, 3) else '0',
                        'shares': current_tab.table.item(row, 4).text() if current_tab.table.item(row, 4) else '0',
                        'status': current_tab.table.item(row, 5).text() if current_tab.table.item(row, 5) else '未知'
                    })
                except Exception as e:
                    print(f"跳过第{row}行，数据不完整: {str(e)}")

            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            default_name = f"数据采集_{timestamp}.xlsx"

            file_dialog = QFileDialog()
            file_dialog.setDefaultSuffix("xlsx")
            file_dialog.setAcceptMode(QFileDialog.AcceptSave)
            file_dialog.setNameFilter("Excel文件 (*.xlsx)")
            file_dialog.setDirectory(self.last_export_path or os.path.expanduser('~/Desktop'))
            file_dialog.selectFile(default_name)

            if file_dialog.exec_() != QFileDialog.Accepted:
                return

            path = file_dialog.selectedFiles()[0]
            if not path.endswith('.xlsx'):
                path += '.xlsx'

            ExportHandler.export_to_excel(data, path)
            self.last_export_path = os.path.dirname(path)
            self.update_status.emit('success', f'导出成功: {os.path.basename(path)}')

        except Exception as e:
            self.update_status.emit('error', f'导出失败: {str(e)}')

    def copy_selected_rows(self):
        current_tab = self.current_tab()
        selected = current_tab.table.selectedItems()
        rows = sorted({item.row() for item in selected})

        text = []
        for row in rows:
            row_data = [
                current_tab.table.item(row, col).text()
                for col in range(current_tab.table.columnCount())
            ]
            text.append('\t'.join(row_data))

        QApplication.clipboard().setText('\n'.join(text))

    def handle_status_update(self, msg_type, message):
        """使用常量更新状态栏样式"""
        color = self.STATUS_COLORS.get(msg_type, '#333333')
        self.status_labels['input'].setText(message)
        self.status_labels['input'].setStyleSheet(f"color: {color};")

    def handle_timer_change(self, text):
        if text == "自定义...":
            dialog = CustomTimerDialog(self)
            if dialog.exec_() == QDialog.Accepted:
                hours = dialog.input_hours.value()
                self._update_timer_combo(f"自定义 ({hours}小时)", hours * 60)
                self.scheduler.create_task(
                    interval=hours * 60,
                    job_func=self._start_crawling
                )

    def _update_timer_combo(self, display_text, minutes):
        self.timer_combo.blockSignals(True)
        indexes = [i for i in range(self.timer_combo.count())
                   if "自定义" in self.timer_combo.itemText(i)]
        for i in reversed(indexes):
            self.timer_combo.removeItem(i)
        self.timer_combo.addItem(display_text, minutes)
        self.timer_combo.setCurrentIndex(0)
        self.timer_combo.blockSignals(False)

    def closeEvent(self, event):
        """立即关闭处理（取消延迟）"""
        logger.info("开始立即关闭程序...")

        # 1. 停止控制器
        if hasattr(self, '_controller'):
            self._controller.stop()

        # 2. 立即关闭调度器
        if hasattr(self, 'scheduler'):
            self.scheduler.safe_shutdown()

        # 3. 强制关闭数据库连接
        from database import DatabaseManager
        DatabaseManager().close_all()

        # 4. 清理Qt资源
        QApplication.processEvents()
        self.deleteLater()

        # 立即接受关闭事件
        event.accept()
        logger.info("程序已立即关闭")

    def _check_resources(self):
        """检查系统资源使用情况"""
        try:
            mem = psutil.virtual_memory().percent
            if mem > 85:  # 内存超过85%时停止爬虫
                if hasattr(self, '_controller') and self._controller.is_running():
                    logger.warning(f"内存使用过高({mem}%)，停止爬虫")
                    self._controller.stop()
        except Exception as e:
            logger.error(f"资源监控错误: {str(e)}")

    def safe_update_status(self, msg_type, message):
        if QThread.currentThread() != self.thread():
            QMetaObject.invokeMethod(self, "update_status",
                                     Qt.QueuedConnection,
                                     Q_ARG(str, msg_type),
                                     Q_ARG(str, message))
        else:
            self.update_status.emit(msg_type, message)

    def reload_cookies(self):
        """重新加载Cookie配置"""
        try:
            # 更新DataProcessor
            DataProcessor().cookie_config.read('cookies.ini', encoding='utf-8')
            self.update_status.emit('success', 'Cookie配置已更新')
        except Exception as e:
            self.update_status.emit('error', f'配置重载失败: {str(e)}')

class CookieSettingsDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Cookie设置")
        self.setMinimumSize(600, 400)
        self.config = ConfigParser()
        self.config.read('cookies.ini', encoding='utf-8')
        self._safe_load_config()  # 安全加载配置
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # 滚动区域
        scroll = QScrollArea()
        content = QWidget()
        scroll.setWidget(content)
        scroll.setWidgetResizable(True)

        form_layout = QVBoxLayout(content)

        # 平台列表（20个平台）
        platforms = [
            "微博", "抖音", "百度贴吧", "今日头条",
            "快手", "小红书", "微信", "知乎",
            "哔哩哔哩", "ACFUN","腾讯视频", "搜狐",
            "网易", "UC", "一点资讯", "度小视",
            "QQ浏览器","好看视频", "网易新闻", "搜狐新闻"
        ]

        # 为每个平台添加输入框
        self.inputs = {}
        for platform in platforms:
            box = QVBoxLayout()
            label = QLabel(f"{platform} Cookie:")
            input_field = QLineEdit()
            input_field.setPlaceholderText(f"输入{platform}的Cookie...")

            # 加载已保存的Cookie
            if self.config.has_section(platform):
                input_field.setText(self.config[platform].get('cookie', ''))

            box.addWidget(label)
            box.addWidget(input_field)
            form_layout.addLayout(box)
            self.inputs[platform] = input_field

        # 按钮区域
        btn_layout = QHBoxLayout()
        save_btn = QPushButton("保存")
        save_btn.clicked.connect(self.save_cookies)
        cancel_btn = QPushButton("取消")
        cancel_btn.clicked.connect(self.reject)

        btn_layout.addStretch()
        btn_layout.addWidget(cancel_btn)
        btn_layout.addWidget(save_btn)

        layout.addWidget(scroll)
        layout.addLayout(btn_layout)
        self.setLayout(layout)

    def _safe_load_config(self):
        """安全加载配置文件（强制UTF-8编码）"""
        try:
            if os.path.exists('cookies.ini'):
                with open('cookies.ini', 'r', encoding='utf-8') as f:
                    # 跳过可能的BOM头
                    content = f.read()
                    if content.startswith('\ufeff'):
                        content = content[1:]
                    self.config.read_string(content)
        except Exception as e:
            QMessageBox.warning(self, "警告", f"加载配置失败: {str(e)}")

    def save_cookies(self):
        """增强版保存Cookie方法，包含完善的错误处理"""
        try:
            # 1. 创建临时配置文件
            temp_config = ConfigParser()

            # 2. 收集所有输入并验证
            for platform, field in self.inputs.items():
                cookie = field.text().strip()
                if cookie:
                    temp_config[platform] = {'cookie': cookie}

            # 3. 创建临时文件路径
            temp_dir = tempfile.gettempdir()
            temp_path = os.path.join(temp_dir, f"cookies_temp_{os.getpid()}.ini")

            # 4. 先写入临时文件
            try:
                with open(temp_path, 'w', encoding='utf-8') as f:
                    temp_config.write(f)
            except IOError as e:
                logger.error(f"写入临时文件失败: {str(e)}")
                raise RuntimeError("无法创建临时配置文件，请检查磁盘空间和权限")

            # 5. 原子替换原文件
            try:
                # 检查目标文件是否可写
                if os.path.exists('cookies.ini'):
                    if not os.access('cookies.ini', os.W_OK):
                        raise RuntimeError("没有写入cookies.ini文件的权限")

                # 执行原子替换
                if os.path.exists(temp_path):
                    shutil.move(temp_path, 'cookies.ini')
            except Exception as e:
                logger.error(f"替换文件失败: {str(e)}")
                # 清理临时文件
                if os.path.exists(temp_path):
                    try:
                        os.remove(temp_path)
                    except:
                        pass
                raise RuntimeError("无法更新配置文件，请检查文件权限")

            # 6. 更新内存配置
            self.config = temp_config
            QMessageBox.information(self, "成功", "Cookie配置已保存！")
            self.accept()

        except Exception as e:
            logger.error(f"保存Cookie配置失败: {str(e)}", exc_info=True)
            error_msg = f"保存失败:\n{str(e)}\n\n可能原因:\n"
            if isinstance(e, PermissionError):
                error_msg += "- 没有文件写入权限\n"
                error_msg += "- 文件可能被其他程序锁定\n"
                error_msg += "- 尝试以管理员身份运行程序\n"
            else:
                error_msg += "- 配置文件格式错误\n"
                error_msg += "- 磁盘空间不足\n"

            QMessageBox.critical(
                self,
                "错误",
                error_msg + "\n建议:\n1. 检查文件是否被其他程序打开\n2. 尝试重启程序"
            )

    def validate_config_file(self):
        """启动时验证配置文件完整性"""
        config_path = 'cookies.ini'
        if not os.path.exists(config_path):
            return

        try:
            with open(config_path, 'rb') as f:
                content = f.read().decode('utf-8-sig')  # 自动处理BOM

            # 简单验证是否包含有效section
            if not re.search(r'^\[.+\]$', content, re.MULTILINE):
                logger.warning("检测到损坏的配置文件，正在修复...")
                self.repair_config_file(content)
        except Exception as e:
            logger.error(f"配置文件验证失败: {str(e)}")

    def _reload_config_chain(self):
        """配置重载联动"""
        # 1. 更新当前对话框配置
        self.config.read('cookies.ini')

        # 2. 更新主窗口数据处理器
        QTimer.singleShot(0, self._notify_main_window)

    def check_config_file(self):
        """启动时检查配置文件完整性"""
        config_path = 'cookies.ini'
        if not os.path.exists(config_path):
            return

        try:
            # 快速验证文件格式
            with open(config_path, 'rb') as f:
                first_line = f.readline().decode('utf-8-sig', errors='ignore')
                if not first_line.lstrip().startswith('['):
                    self.repair_config_file()
        except Exception as e:
            logger.warning(f"配置文件检查失败: {str(e)}")

    def repair_config_file(self):
        """自动修复配置文件"""
        try:
            # 备份原文件
            bak_path = f'cookies.ini.bak.{time.time()}'
            shutil.copyfile('cookies.ini', bak_path)

            # 读取内容并重新格式化
            with open('cookies.ini', 'rb') as f:
                content = f.read().decode('utf-8-sig')

            new_config = ConfigParser()
            if '=' in content:
                # 尝试提取平台名
                platform = '微博' if 'weibo' in content.lower() else 'Default'
                new_config[platform] = {'cookie': content.strip()}

                # 写入新文件
                with open('cookies.ini', 'w', encoding='utf-8') as f:
                    new_config.write(f)

            logger.info("已自动修复配置文件")
        except Exception as e:
            logger.error(f"修复配置文件失败: {str(e)}")

    @pyqtSlot()
    def _notify_main_window(self):
        """通知主窗口重新加载配置"""
        if parent := self.parent():
            try:
                # 通过父窗口方法更新
                if hasattr(parent, 'reload_cookies'):
                    parent.reload_cookies()
                # 同时更新全局处理器
                DataProcessor().cookie_config.read('cookies.ini')
            except Exception as e:
                logger.error(f"配置更新通知失败: {str(e)}")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

